Apollo Client with GraphQL integration into a React Application
Introduction
The blog’s agenda is to give a brief explanation of GraphQL and Apollo Client with its setup and integration into a React project.
GraphQL
Generally, an application design includes API endpoints for querying which comes with the drawback of over-fetching or under-fetching the required data. The impact is more significant for larger enterprises with billions and millions of requests. The cost for the server just gets expensive because the amount of data payload is being queried and mutated over a second. In this scenario instead of multiple URLs, GraphQL is an API query language that has a single point entry capable of retrieving selective information. For more information on GraphQL here.
Apollo Client
Apollo Client is a state management library that enables easy local and remote data management with GraphQL. Apollo Client's intelligent caching and declarative approach to data fetching can help you iterate faster while writing less code. Additionally, you can create your dream client by creating extensions over the Apollo Client if you need customization. Some interesting features of Apollo Client include:
Declarative data fetching
Apollo Clients can handle the request cycle from start to end, including loading and error states. You need not worry about adding the middleware or transforming or caching responses. All you need to do is describe the data that your component needs and let Apollo do the heaving lifting.
Combining local and remote data
Apollo Client uses Local State Management features enabling you to use the cache as the single source of truth for your application’s data. By using Apollo Client you can combine local fields and remotely fetched fields in the same query.
Zero-config Caching
One of the key features that sets Apollo Client apart from other data management solutions is its local, in-memory cache. Caching a graph was never an easy task. The only way was to normalize and maintain consistent data across the multiple components in an application. But Apollo Client helps in these scenarios by enabling in-memory-cache, this avoids unnecessary fetching which results in improved performance.
Vibrant Ecosystem
To build out more advanced features, Apollo Client comes with custom functionality i.e. Apollo Link's architecture
that supports @apollo/client to create one's dream client by building an extension on top of Apollo Client.
Some of the community's extensions include: apollo3-cache-persist, apollo-storybook-decorator, AppSync by AWS, etc.
Implementation of Apollo Client
Prerequisite
Create a new React project locally with create react app.
Install Apollo Client Package
Install the package required for Apollo Client and GraphQL.
npm install @apollo/client graphql
Initialization of Apollo Client
Let's initialize Apollo Client by initializing its constructor the below values which are URI and cache.
For example, I'm using the rickandmortyapi
endpoint, and the URL is https://rickandmortyapi.com/graphql.
const client = new ApolloClient({ uri: 'https://rickandmortyapi.com/graphql', cache: new InMemoryCache() });
URI
specifies the GraphQL server. Cache
is an instance of InMemoryCache. On querying, Apollo Client uses InMemoryCache to cache the results. On any new request, we first check for a hit in the cache, else we proceed as a new request to the endpoint.
Connect apolloClient to React
We can connect Apollo Client to React by wrapping the root component with ApolloProvider.
<ApolloProvider client={client}> <App /> </ApolloProvider>
Simple Query Template for Fetching
Create a query named GET_ALL_CHAR
and wrap the query string in the gql
function. gql
is a tag from @apollo/client, used to write and parse a GraphQL query into a standard GraphQL AST. Query string includes the actual GraphQL query. We start with query
and continue the query structure matching the schema. Here's a simple example, for more details on syntax read further.
const GET_ALL_CHAR = gql` query GetAllCharacters{ characters { results { id name image } } } `;
Query Execution
Apollo Client uses a React hook called useQuery
to bind a query to your component. This enables the component to render a query’s result immediately. The hook useQuery encapsulates the logic for retrieving data, tracking the request, loading, and error handling. This helps your component to bind to query and update the component.
const { error, data, loading } = useQuery(GET_ALL_CHAR);
Implementation
Here are some important code snippets. For complete source code here.
src/index.js
import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; import { ApolloClient, InMemoryCache, ApolloProvider, } from '@apollo/client'; import { BrowserRouter } from 'react-router-dom'; const client = new ApolloClient({ uri: 'https://rickandmortyapi.com/graphql', cache: new InMemoryCache(), }); const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <React.StrictMode> <BrowserRouter> <ApolloProvider client={client}> <App /> </ApolloProvider> </BrowserRouter> </React.StrictMode> );
src/App.js
import CharacterList from './pages/CharacterList'; import Character from './pages/Character'; import { Route, Routes } from 'react-router'; function App() { return ( <div className='App'> <Routes> <Route strict exact path='/' element={<CharacterList />} /> <Route strict exact path='/:id' element={<Character />} /> </Routes> </div> ); } export default App;
src/pages/CharacterList.js
import React from 'react'; import './CharacterList.css'; import { useCharactersDisplay } from '../hooks/useCharactersDisplay'; import { Link } from 'react-router-dom'; export default function CharacterList() { const { error, data, loading } = useCharactersDisplay(); if (loading) return <div>...loading....</div> if( error) return <div>.....error</div> return <div> {data.characters.results.map(character => { return ( <br /> <img src="{character.image}" alt='character' /> <div>{character.name}</div> <br /> ); } )} </div>; }
src/hooks/useCharactersDisplay
import { useQuery, gql } from '@apollo/client'; const GET_ALL_CHAR = gql` query GetAllCharacters{ characters { results { id name image } } } `; export const useCharactersDisplay = props => { const { error, data, loading } = useQuery(GET_ALL_CHAR); return {error, data, loading}; };
How to write a Query String
As a fresher to GraphQL and Apollo Client, drafting a query might seem slightly challenging. I would like to share how to write one.
Understanding Schema
Let's consider an example server endpoint "https://rickandmortyapi.com/graphql". To write a query, it's most important to understand the schema.
In the above schema, we can see the object types and fields on those types.
For now, let's focus on type Character
and type Characters
.
These are object types with their fields. Fields as shown in the image describe their type as pointing to a primary data type as String, ID, Int or even to object. For example, the field results
in Characters
tells its type Character
, and enclosures with '[]' indicate that it returns a list of character objects.
type Query
is a special object type that defines top-level entry points for querying. Here let's consider the Query endpoint: character(id: ID!): Character
. character accepts an argument of id
and returns a Character object.!
indicates that the argument is mandatory.
Query without Arguments
While writing the query, please refer to the field structure in the docs. To get the query right, it is important to match the shape of the object types in the schema.
- Line 1, Rather than writing the whole query in the useQuery hook, we can do so by assigning the query to the variable.
- enclosing with gql from
@apollo/client
is mandatory as this notifies the compiler to parse as a query. - Line 2, For querying, we start with a
query
keyword followed by a name (which can be anonymously when there is no argument sent) and open the body with{...}
. - Line 3, In the query string we start with the root type name, here it is
characters
then proceed toresults
. - Line 4,
results
is a field pointing to an object list, hence I must specify the required field in that object list, where I choose it to be name and image.
const GET_ALL_CHAR = gql` query GetAllCharacters { characters { results { name image } } } `;
Results from GraphQL are in JSON format.
Query with Arguments
Now let's consider the character
endpoint, which has ID as a mandatory argument.
const GET_CHAR = gql` query GetCharacter($id: ID!) { character(id: $id) { name gender image episode { name air_date } } } `;
In association with an argument in the schema, while executing the query we shall pass the argument as variables.
export const useCharacter = (id) => { const { error, data, loading } = useQuery(GET_CHAR, { variables:{ id } }); return { error, data, loading }; }
References
- https://www.apollographql.com/docs/react/
- https://graphql.org/learn/
- https://www.youtube.com/watch?v=gAbIQx26wSI